﻿// <copyright file="Steamer.cs" company="The Amuse project">
//     Copyright © 2009, Nidonocu and other Contributors. All rights reserved.
//     Licensed under the BSD License - See 'Amuse License.txt' for full details.
// </copyright>

namespace Nox.Amuse.Net
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.IO;
    using System.Net.Sockets;
    using System.Diagnostics;

    /// <summary>
    /// Manages the data stream of a session
    /// </summary>
    public static class Streamer
    {
        /// <summary>
        /// The size of the buffer to use
        /// </summary>
        const int BufferSize = 1024;

        /// <summary>
        /// Reads data from the data stream and processes it in to an array of bytes for processing
        /// This method blocks until the connection closes
        /// </summary>
        /// <param name="stream">The stream to read data from</param>
        /// <param name="receivedDelegate">A delegate for the method to invoke when data has been received</param>
        public static ConnectionResult PerformListening(Stream stream, DataReceivedHandler receivedDelegate)
        {
            if (stream == null)
                throw new ArgumentException("Stream object must be provided");
            if (receivedDelegate == null)
                throw new ArgumentException("Delegate must be provided");

            var numberOfBytesRecieved = 0;
            BufferedStream bufferStream = new BufferedStream(stream, BufferSize);
            byte[] buffer = new byte[BufferSize];
            try
            {
                numberOfBytesRecieved = bufferStream.Read(buffer, 0, buffer.Length);
                while (numberOfBytesRecieved > 0)
                {
                    receivedDelegate.Invoke(numberOfBytesRecieved, buffer);
                    numberOfBytesRecieved = bufferStream.Read(buffer, 0, buffer.Length);
                }
            }
            catch (SocketException sockFault)
            {
                Debug.Print("Socket Exception: " + sockFault.Message);
                return new ConnectionResult
                {
                    Error = Connector.IdentifySocketError(sockFault),
                    ErrorMessage = sockFault.Message
                };
            }
            catch (IOException ioFault)
            {
                Debug.Print("IO Exception: " + ioFault.Message);
                var inner = ioFault.InnerException as SocketException;
                if (inner != null)
                {
                    return new ConnectionResult
                    {
                        Error = Connector.IdentifySocketError(inner),
                        ErrorMessage = inner.Message
                    };
                }
                else
                    return new ConnectionResult
                    {
                        Error = ConnectionErrorTypes.Broken,
                        ErrorMessage = ioFault.Message
                    };
            }
            finally
            {
                stream.Close();
            }
            return new ConnectionResult { Error = ConnectionErrorTypes.None };
        }

        /// <summary>
        /// Send data provided as bytes down the given stream
        /// </summary>
        /// <param name="stream">The stream to send the data to</param>
        /// <param name="data">The bytes to send</param>
        /// <returns>True if the send was successful</returns>
        public static bool SendBytes(Stream stream, byte[] data)
        {
            if (stream.CanWrite)
            {
                try
                {
                    stream.Write(data, 0, data.Length);
                    return true;
                }
                catch (IOException ioFault)
                {
                    Debug.Print("IO Exception: " + ioFault.Message);
                    return false;
                }
                catch (ObjectDisposedException disposeFault)
                {
                    Debug.Print("ObjectDisposedException Exception: " + disposeFault.Message);
                    return false;
                }
            }
            else
                return false;
        }

        /// <summary>
        /// The Carriage Return and Line Feed characters
        /// </summary>
        public const string CarriageReturnLineFeed = "\r\n";

        /// <summary>
        /// Send a command provided as text down the open network stream
        /// </summary>
        /// <param name="stream">The stream to send the text to</param>
        /// <param name="command">The command to send</param>
        /// <returns>True if the send was successful</returns>
        public static bool SendText(Stream stream, string command)
        {
            if (stream.CanWrite)
            {
                try
                {
                    string text = command + CarriageReturnLineFeed;
                    Byte[] data = Encoding.Default.GetBytes(text);
                    stream.Write(data, 0, data.Length);
                    return true;
                }
                catch (IOException ioFault)
                {
                    Debug.Print("IO Exception: " + ioFault.Message);
                    return false;
                }
                catch (ObjectDisposedException disposeFault)
                {
                    Debug.Print("ObjectDisposedException Exception: " + disposeFault.Message);
                    return false;
                }
            }
            else
                return false;
        }
    }

    /// <summary>
    /// Represents a method that will deal with received data from a Listener
    /// </summary>
    /// <param name="numberOfBytesReceived">The number of bytes that were recieved</param>
    /// <param name="data">The data that was received from the socket</param>
    public delegate void DataReceivedHandler(int numberOfBytesReceived, byte[] data);

    /// <summary>
    /// Defines the results from a TelnetCharacterProcessing operation
    /// </summary>
    public sealed class TelnetCharacterProcessingResult
    {
        /// <summary>
        /// Gets or sets if the the suboption being processed is Invalid
        /// </summary>
        public bool IsInvalidSubOption { get; set; }
        /// <summary>
        /// Gets or sets the current stage of the processing operation
        /// </summary>
        public TelnetCommandStage TelnetCommandStage { get; set; }
        /// <summary>
        /// Gets or sets the type of command that was issued
        /// </summary>
        public TelnetCommandType TelnetCommandType { get; set; }
        /// <summary>
        /// Gets or sets the command that was issued
        /// </summary>
        public AllowedTelnetCommand TelnetAllowedCommands { get; set; }
    }
}
